package net.hands.common.web.html;

import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.hands.common.Convert;
import net.hands.common.StringUtils;

import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.parser.Parser;


/**
 * 说明:html工具类  
 * <br>
 * <p>这是一个开源的软件，请在LGPLv3下合法使用、修改或重新发布。</p>
 * @author 陈侃(cknet@126.com, 13910090885)
 *         http://code.google.com/p/simpleframework/
 *         http://www.simpleframework.net
 */
public abstract class HtmlUtils {
   /**
   * 说明:创建dom  在html标签内  生成html属性及其head
   * @param htmlString html字符串 
   * @param wrapHtml  true 是在html标签内  false 在html标签外
   * @return Document
   * @update 2012-9-20 下午4:15:23
    */
	public static Document createHtmlDocument(final String htmlString, final boolean wrapHtml) {
		//建立dom
		final Document document = new Document("");
		//创建htmldom
		final Element html = document.createElement("html");
		//节点集合
		final List<Node> nodeList = Parser.parseFragment(StringUtils.blank(htmlString), html,
				document.baseUri());
		for (final Node node : nodeList.toArray(new Node[nodeList.size()])) {
			if (wrapHtml) {//是否外围html标签
				html.appendChild(node);
			} else {
				document.appendChild(node);
			}
		}
		if (wrapHtml) {//是否外围html标签
			document.appendChild(html);//dom加入html标签
			if (!StringUtils.hasText(html.attr("xmlns"))) {//加入hrml属性
				html.attr("xmlns", "http://www.w3.org/1999/xhtml");
			}
			Element head = html.select(">head").first();//获得第一个head标签
			if (head == null) {//如果head标签为null就创建该标签
				html.prependChild(head = document.createElement("head"));
			}
		}
		document.outputSettings().prettyPrint(false);
		return document;
	}
	/**
	* 说明:缩短html
	* @param htmlString html字符
	* @param length 长度
	* @param newLine 新行
	* @return String
	* @update 2012-9-20 下午4:23:02
	 */
	public static String truncateHtml(final String htmlString, final int length,
			final boolean newLine) {
		return truncateHtml(createHtmlDocument(htmlString, false), length, newLine);
	}
   /**
   * 说明:缩短html 
   * @param doc 文档
   * @param length 长度
   * @param newLine 新行
   * @return String
   * @update 2012-9-20 下午4:24:23
    */
	public static String truncateHtml(final Document doc, final int length, final boolean newLine) {
		return truncateHtml(doc, length, newLine, true, false);
	}
   /**
   * 说明:缩短html 
   * @param doc 文档
   * @param length 长度
   * @param newLine 新行
   * @param showLink 显示连接
   * @param dot    是否省略
   * @return String
   * @update 2012-9-20 下午4:25:02
    */
	public static String truncateHtml(final Document doc, final int length, final boolean newLine,
			final boolean showLink, final boolean dot) {
		doc.attr("length", String.valueOf(length));// 文档长度
		String html = elementText(doc, doc.childNodes(), newLine, showLink);
		if (dot) {
			if (Convert.toInt(doc.attr("length")) <= 0) {
				html += "...";
			}
		}
		doc.removeAttr("length");
		doc.removeAttr("br");
		return html;
	}
   /**
    * 说明:元素内容 
   * @param doc  文旦
   * @param nodes 节点集合
   * @param newLine 是否是新行
   * @param showLink 显示连接
   * @return String
   * @update 2012-9-20 下午4:26:52
    */
	private static String elementText(final Document doc, final List<Node> nodes,
			final boolean newLine, final boolean showLink) {
		final StringBuilder sb = new StringBuilder();
		for (final Node child : nodes) {//遍历节点集合
			final int length = Convert.toInt(doc.attr("length"));
			if (length <= 0) {
				break;
			}
			if (child instanceof TextNode) {//子节点为内容节点
				final String txt = ((TextNode) child).text();//直接获得内容
				if (StringUtils.hasText(txt)) {//如果内容存在
					sb.append(HtmlEncoder.text(StringUtils.substring(txt, length)));
					doc.attr("length", String.valueOf(length - txt.length()));
					doc.removeAttr("br");
				}
			} else if (child instanceof Element) {//子节点为元素节点
				final Element element = (Element) child;
				String href;
				if (showLink && "a".equalsIgnoreCase(element.tagName())//元素是否为a链接
						&& element.children().size() == 0
						&& StringUtils.hasText(href = element.attr("href"))
						&& !href.toLowerCase().startsWith("javascript:")) {
					doc.attr("length", String.valueOf(length - element.text().length()));
					element.removeAttr("style").removeAttr("class").attr("target", "_blank");//元素移除样式,样式名称和跳转目标
					sb.append(element.outerHtml());
				} else {//其他
					String txt = elementText(doc, element.childNodes(), newLine, showLink);
					txt = StringUtils.replace(txt, "&nbsp;", " ").trim();
					if (StringUtils.hasText(txt)) {//内容为空
						sb.append(txt);
						if (newLine && element.isBlock() && Convert.toInt(doc.attr("length")) > 0) {//是否新启动一行并且是大字段,长度大于0
							if (!doc.attr("br").equals("true")) {
								sb.append("<br style=\"margin-bottom: 4px;\" />");
								doc.attr("br", "true");
							}
						}
					}
				}
			}
		}
		return sb.toString();
	}
   /**
    * script正则表达式模型
    */
	private static final Pattern EXPR_PATTERN = Pattern
			.compile("<script[^>]*>([\\S\\s]*?)</script>");
  /**
  * 说明:脱去script脚本
  * @param content 脚本内容
  * @return String
  * @update 2012-9-20 下午4:58:02
   */
	public static String stripScripts(final String content) {
		if (StringUtils.hasText(content)) {
			return EXPR_PATTERN.matcher(content).replaceAll("");
		} else {
			return StringUtils.blank(content);
		}
	}
   /**
   * 说明:转换html行
   * @param input html 字符串
   * @return String
   * @update 2012-9-20 下午4:59:08
    */
	public static final String convertHtmlLines(final String input) {
		return StringUtils.replace(StringUtils.replace(StringUtils.blank(input), "\n", "<br/>"),
				"\r", "").replace("\t", "&nbsp;&nbsp;");
	}
   /**
    * 
   * 说明：判断是否有标签
   * @param input html 内容
   * @return boolean
   * @update 2012-10-8 下午12:47:22
    */
	public static boolean hasTag(final String input) {
		for (final Object o : Parser.parseFragment(StringUtils.blank(input), new Document(""), "")) {
			if (!(o instanceof TextNode)) {
				return true;
			}
		}
		return false;
	}
   /**
    * 连接表达式模型
    */
	private static Pattern url_pattern;
   /**
    * 
   * 说明：超连接<a target="_blank" href="src">src</a>
   * @param txt 带连接内容
   * @return String
   * @update 2012-10-8 下午12:49:23
    */
	public static String autoLink(final String txt) {
		if (url_pattern == null) {
			url_pattern = Pattern.compile("(http(s)?|ftp)://([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?");
		}
		final StringBuilder html = new StringBuilder();
		int lastIdx = 0;
		final Matcher matchr = url_pattern.matcher(txt);
		while (matchr.find()) {
			final String str = matchr.group();
			html.append(txt.substring(lastIdx, matchr.start()));
			html.append("<a target=\"_blank\" href=\"");
			html.append(str).append("\">").append(str).append("</a>");
			lastIdx = matchr.end();
		}
		html.append(txt.substring(lastIdx));
		return html.toString();
	}
   /**
   *  说明:转html标签字符
   * @param tagName 标签名
   * @param properties 属性集合
   * @return  String
   * @update 2012-9-20 下午3:13:58
    */
	public static String tag(final String tagName, final Properties properties) {
		return tag(tagName, null, properties);
	}
	/**
	   * 说明:生成html标签字符串
	   * @param tagName 标签名
	   * @param text  内容
	   * @param properties 属性集合
	   * @return String
	   * @update 2012-9-20 下午3:19:11
	    */
	public static String tag(final String tagName, final String text, final Properties properties) {
		if (!StringUtils.hasText(tagName)) {//标签名不存在 返回null
			return "";
		}
		final StringBuilder sb = new StringBuilder();
		sb.append("<").append(tagName);//标签名
		if (properties != null) {
			for (final Entry<Object, Object> entry : properties.entrySet()) {
				sb.append(" ").append(entry.getKey()).append("=\"");
				sb.append(HtmlEncoder.text((String) entry.getValue()));
				sb.append("\"");
			}
		}
		if (text != null) {//标签中间内容
			sb.append(">");
			sb.append(text).append("</").append(tagName).append(">");
		} else {
			sb.append("/>");
		}
		return sb.toString();
	}
}
